import { BaseConnection, MsgCall } from "tsrpc";
import { ServiceType } from "../../shared/protocols/serviceProto_worldServer";
import { IUserData, } from "../../shared/types/SubWorldData";
import { WorldServerConn } from "../WorldServer";
import { SubWorldConfigItem } from "../../shared/SubWorldConfig";
import { MsgAdjustCue } from "../../shared/protocols/worldServer/billiards/MsgAdjustCue";
import { MsgHitBall } from "../../shared/protocols/worldServer/billiards/MsgHitBall";
import { MsgHitBallComplete } from "../../shared/protocols/worldServer/billiards/MsgHitBallComplete";
import { IBilliardsPlayer, IBilliardsGameData } from "../../shared/protocols/worldServer/billiards/BilliardsTypeDef";
import { SubWorld } from "./SubWorld";
import { MsgBallsDataSync } from "../../shared/protocols/worldServer/billiards/MsgBallsDataSync";

export class SubWorldBilliards extends SubWorld {

    private _gameData: IBilliardsGameData = {
        players: [],
        currentPlayer: '',
        ballsData: undefined,
        curDirection: undefined
    }

    constructor(id: string, displayId: string, name: string, config: SubWorldConfigItem, password?: string) {
        super(id, name, displayId, config, password);
        this.data.maxUser = 50;
        this.data.maxPlayerNum = 2;
    }

    listenMsgs(conn: WorldServerConn) {
        super.listenMsgs(conn);

        conn.listenMsg("billiards/AdjustCue", call => { this.onMsg_AdjustCue(call); });
        conn.listenMsg("billiards/HitBall", call => { this.onMsg_HitBall(call) });
        conn.listenMsg("billiards/HitBallComplete", call => { this.onMsg_HitBallComplete(call) });
        conn.listenMsg("billiards/BallsDataSync", call => { this.onMsg_BallsDataSync(call) });
    }

    unlistenMsgs(conn: WorldServerConn) {
        super.unlistenMsgs(conn);
        conn.unlistenMsgAll("billiards/AdjustCue");
        conn.unlistenMsgAll("billiards/HitBall");
        conn.unlistenMsgAll("billiards/HitBallComplete");
        conn.unlistenMsgAll("billiards/BallsDataSync");
    }

    onDisconnected(conn: WorldServerConn): void {
        super.onDisconnected(conn);
        let user = (conn as WorldServerConn).currentUser;
        if (!user) {
            return;
        }

        if (this.data.isPlaying) {
            if (user.uid == this._gameData.currentPlayer) {
                if (this._gameData.currentPlayer == this._gameData.players[0].uid) {
                    this._gameData.currentPlayer = this._gameData.players[1].uid;
                }
                else {
                    this._gameData.currentPlayer = this._gameData.players[0].uid;
                }
                this.broadcastMsg("billiards/GameDataChangedPush", { currentPlayer: this._gameData.currentPlayer });
            }
        }
    }

    onUserEnter(conn: WorldServerConn) {
        conn.sendMsg("billiards/GameDataSyncPush", this._gameData);
    }

    onPlayerLeave(conn: WorldServerConn) {
        for (let i = 0; i < this._gameData.players.length; ++i) {
            let p = this._gameData.players[i];
            if (p.uid == conn.currentUser?.uid) {
                this._gameData.players.splice(i, 1);
                this.broadcastMsg("billiards/PlayerLeavesPush", { uid: p.uid });
                if (this._gameData.players.length == 0) {
                    this._gameData.currentPlayer = '';
                    this.broadcastMsg("billiards/GameDataChangedPush", { currentPlayer: this._gameData.currentPlayer });
                }
                break;
            }
        }
        if (this._data.isPlaying) {
            this.broadcastMsg("billiards/GameOverPush", { winner: this._gameData.players[0].uid });
            this._data.isPlaying = false;
            this.broadcastMsg("SubWorldDataChangedPush", { isPlaying: this._data.isPlaying });
        }
    }

    onJoinGame(newUser: IUserData) {

        let newPlayer: IBilliardsPlayer = {
            uid: newUser.uid,
            pocketedBalls: []
        };

        //add to player list
        this._gameData.players.push(newPlayer);
        this.broadcastMsg("billiards/PlayerComesPush", { player: newPlayer });

        //notify to remove from watcher list.

        //set as current player
        if (!this._gameData.currentPlayer) {
            this._gameData.currentPlayer = newPlayer.uid;
            //
            this.broadcastMsg("billiards/GameDataChangedPush", { currentPlayer: this._gameData.currentPlayer });
        }

        return true;
    }

    onCheckGameBegin() {
        if (this.data.playerNum < this.data.maxPlayerNum) {
            return;
        }

        let readyCnt = 0;
        this._gameData.players.forEach(v => {
            if (this._userMap.get(v.uid)?.ready) {
                readyCnt++;
            }
        });

        if (readyCnt == 2) {
            this.onResetGameData();
            this._data.isPlaying = true;
            this.broadcastMsg("SubWorldDataChangedPush", { isPlaying: this._data.isPlaying });

            let p = this._gameData.players[Math.floor(Math.random() * 2)];
            this._gameData.currentPlayer = p.uid;

            this.broadcastMsg("billiards/GameDataChangedPush", { currentPlayer: this._gameData.currentPlayer });
            this.broadcastMsg("billiards/GameBeginPush", {});
        }
    }

    onResetGameData() {
        this._gameData.currentPlayer = '';
        this._data.isPlaying = false;
        this._gameData.ballsData = undefined;
        this._gameData.curDirection = undefined;
        this._gameData.players.forEach(v => {
            (v as IBilliardsPlayer).pocketedBalls = [];
        });
    }

    /***
 * @en get player by connnection, null means not a player.
 * @zh 通过链接查找对应的玩家, null 表示不是玩家
 */
    getTablePlayer(conn: BaseConnection<ServiceType>) {
        let user = (conn as WorldServerConn).currentUser;
        if (!user) {
            return null;
        }

        for (let i = 0; i < this._gameData.players.length; ++i) {
            let p = this._gameData.players[i];
            if (p.uid == user.uid) {
                return p;
            }
        }

        return null;
    }

    gameOver(winner: string) {
        this.data.isPlaying = false;
        for (let i = 0; i < this._gameData.players.length; ++i) {
            let p = this._gameData.players[i];
            this._userMap.get(p.uid)!.ready = false;
        }
        this.broadcastMsg("SubWorldDataChangedPush", { isPlaying: this._data.isPlaying });
        this.broadcastMsg("billiards/GameOverPush", { winner: winner });
    }

    //gameplay network methods.

    onMsg_AdjustCue(call: MsgCall<MsgAdjustCue, ServiceType>) {
        let tablePlayer = this.getTablePlayer(call.conn);
        if (!tablePlayer) {
            return;
        }

        if (tablePlayer.uid != this._gameData.currentPlayer) {
            return;
        }

        this._gameData.curDirection = call.msg.direction;

        this.broadcastMsg("billiards/AdjustCue", call.msg);
    }

    onMsg_HitBall(call: MsgCall<MsgHitBall, any>) {
        let tablePlayer = this.getTablePlayer(call.conn);
        if (!tablePlayer) {
            return;
        }

        if (tablePlayer.uid != this._gameData.currentPlayer) {
            return;
        }

        let data = call.msg;
        this._gameData.ballsData = data.ballsData;
        this.broadcastMsg("billiards/GameDataChangedPush", { ballsData: this._gameData.ballsData });
        this.broadcastMsg("billiards/HitBall", { hitImpulse: data.hitImpulse, order: data.order });
    }

    onMsg_HitBallComplete(call: MsgCall<MsgHitBallComplete, any>) {
        let tablePlayer = this.getTablePlayer(call.conn);
        if (!tablePlayer) {
            return;
        }

        if (tablePlayer.uid != this._gameData.currentPlayer) {
            return;
        }

        let data = call.msg;

        this._gameData.ballsData = data.ballsData;
        this._gameData.currentPlayer = data.nextPlayer;
        data.nextPlayer = '';

        this.broadcastMsg("billiards/HitBallComplete", data);
        this.broadcastMsg("billiards/GameDataChangedPush", { currentPlayer: this._gameData.currentPlayer, ballsData: data.ballsData });
    }

    onMsg_BallsDataSync(call: MsgCall<MsgBallsDataSync, any>) {
        let tablePlayer = this.getTablePlayer(call.conn);
        if (!tablePlayer) {
            return;
        }

        if (tablePlayer.uid != this._gameData.currentPlayer) {
            return;
        }
        this._gameData.ballsData = call.msg.ballsData;

        let hasAdded = false;
        if (call.msg.newPocketedBalls) {
            for (let i = 0; i < call.msg.newPocketedBalls.length; ++i) {
                let ballId = call.msg.newPocketedBalls[i];
                if (tablePlayer.pocketedBalls.indexOf(ballId) == -1) {
                    tablePlayer.pocketedBalls.push(ballId);
                    hasAdded = true;
                }
            }
            if (hasAdded) {
                this.broadcastMsg("billiards/PlayerDataChangedPush", { uid: this._gameData.currentPlayer, pocketedBalls: tablePlayer.pocketedBalls })
            }
        }
        this.broadcastMsg("billiards/GameDataChangedPush", { ballsData: this._gameData.ballsData });
        if (call.msg.gameOver) {
            this.broadcastMsg("billiards/GameOverPush", { winner: tablePlayer.uid });
        }
    }
}